/**
 *  Created on  : 07/05/2012
 *  Author      : Ing. Javier Silva Pérez - [javier]
 *  Description :
 *  	RSA public key abstraction
 */
package cinvestav.cryptography.pki.android.key;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.RSAPublicKeySpec;

import org.spongycastle.crypto.params.RSAKeyParameters;
import org.spongycastle.util.encoders.Base64;
import org.spongycastle.util.encoders.Hex;

import cinvestav.cryptography.pki.android.exception.AndroidCryptoUtilsException;
import cinvestav.cryptography.pki.android.utils.AndroidCryptoUtils;

/**
 * @author Ing. Javier Silva Pérez - [javier]
 * 
 */
public class RSAPublicKey {
	private BigInteger modulus;
	private BigInteger publicExponent;

	/**
	 * Default constructor, initialize modulus and publicExponent in 0
	 */
	public RSAPublicKey() {
		super();
		this.modulus = new BigInteger("0");
		this.publicExponent = new BigInteger("0");
	}

	/**
	 * Copy constructor
	 * 
	 * @param rsaPublicKey
	 *            Source
	 */
	public RSAPublicKey(RSAPublicKey rsaPublicKey) {
		this.modulus = new BigInteger(rsaPublicKey.getModulus().toByteArray());
		this.publicExponent = new BigInteger(rsaPublicKey.getPublicExponent()
				.toByteArray());
	}

	/**
	 * Parameterized constructor, gets the modulus and publicExponent
	 * 
	 * @param modulus
	 * @param publicExponent
	 */
	public RSAPublicKey(BigInteger modulus, BigInteger publicExponent) {
		super();
		this.modulus = modulus;
		this.publicExponent = publicExponent;
	}

	public BigInteger getModulus() {
		return modulus;
	}

	public void setModulus(BigInteger modulus) {
		this.modulus = modulus;
	}

	public BigInteger getPublicExponent() {
		return publicExponent;
	}

	public void setPublicExponent(BigInteger publicExponent) {
		this.publicExponent = publicExponent;
	}

	@Override
	public String toString() {
		return "RSAPublicKey [modulus=" + modulus + ", publicExponent="
				+ publicExponent + "]";
	}
	
	/**
	 * ToString method using a specific encoder
	 * @param encoder see available encoders in {@link cinvestav.cryptography.pki.android.utils.AndroidCryptoUtils}
	 * @return	String Public Key encoded
	 */
	public String toString(String encoder) {
		if (encoder.equalsIgnoreCase(AndroidCryptoUtils.ENCODER_BASE64)) {
			return "RSAPublicKey [modulus="
					+ new String(Base64.encode(modulus.toByteArray()))
					+ ", publicExponent="
					+ new String(Base64.encode(publicExponent.toByteArray())) + "]";

		}
		if(encoder.equalsIgnoreCase(AndroidCryptoUtils.ENCODER_HEX)) {
			return "RSAPublicKey [modulus="
					+ new String(Hex.encode(modulus.toByteArray()))
					+ ", publicExponent="
					+ new String(Hex.encode(publicExponent.toByteArray())) + "]";
		}
		return "RSAPublicKey [modulus=" + modulus + ", publicExponent="
		+ publicExponent + "]";
	}

	/**
	 * Parse a {@link org.spongycastle.crypto.params.RSAKeyParameters} and
	 * stores the result in the current object
	 * 
	 * @param publicKey
	 *            RSAKeyParameters object
	 */
	public void parseRSAPublicKey(RSAKeyParameters publicKey) {
		this.modulus = publicKey.getModulus();
		this.publicExponent = publicKey.getExponent();
	}

	/**
	 * Parse the current instance and creates a new instance of
	 * {@link org.spongycastle.crypto.params.RSAKeyParameters}
	 * 
	 * @return New RSAKeyParameters public key with the values of the current
	 *         object
	 */
	public RSAKeyParameters parseToRSAKeyParameters() {
		return new RSAKeyParameters(false, this.modulus, this.publicExponent);
	}

	/**
	 * Parse an {@link org.spongycastle.asn1.pkcs.RSAPublicKey} object(in pkcs
	 * format) to RSAPublicKey and stores the result in the invoking object
	 * 
	 * @param publicKey
	 *            RSA Public key to be parsed (
	 *            {@link org.spongycastle.asn1.pkcs.RSAPublicKey})
	 */
	public void parseRSAPublicKey(
			org.spongycastle.asn1.pkcs.RSAPublicKey publicKey) {
		this.modulus = publicKey.getModulus();
		this.publicExponent = publicKey.getPublicExponent();

	}

	/**
	 * Parse the current object to
	 * {@link org.spongycastle.asn1.pkcs.RSAPublicKey} (in PKCS format)
	 * 
	 * @return RSAPublicKey in PKCS format
	 */
	public org.spongycastle.asn1.pkcs.RSAPublicKey parseToRSAPublicKey() {
		return new org.spongycastle.asn1.pkcs.RSAPublicKey(this.modulus,
				this.publicExponent);
	}

	/**
	 * Parse an {@link java.security.interfaces.RSAPublicKey} object to
	 * RSAPublicKey and stores the result in the invoking object
	 * 
	 * @param publicKey Java default RSA Public Key object
	 */
	public void parseRSAPublicKey(
			java.security.interfaces.RSAPublicKey publicKey) {
		this.modulus = publicKey.getModulus();
		this.publicExponent = publicKey.getPublicExponent();
	}
	
	/**
	 * Parse the current object to {@link java.security.interfaces.RSAPublicKey}
	 * @return	RSAPublicKey object compatible with JCE
	 * @throws AndroidCryptoUtilsException
	 */
	public java.security.interfaces.RSAPublicKey parseToJCERSAPublicKey() throws AndroidCryptoUtilsException{
		RSAPublicKeySpec keySpec = new RSAPublicKeySpec(
				this.modulus,
                this.publicExponent);

		KeyFactory keyFact;
		try {
			keyFact = KeyFactory.getInstance("RSA", AndroidCryptoUtils.PROVIDER);
			return (java.security.interfaces.RSAPublicKey) keyFact.generatePublic(keySpec);
		} catch (NoSuchAlgorithmException e) {
			// TODO Auto-generated catch block
			throw new AndroidCryptoUtilsException(
					"Parse to JCE RSAPublicKey error: "+e,e.getCause());
		} catch (NoSuchProviderException e) {
			// TODO Auto-generated catch block
			throw new AndroidCryptoUtilsException(
					"Parse to JCE RSAPublicKey error: "+e,e.getCause());
		} catch (InvalidKeySpecException e) {
			// TODO Auto-generated catch block
			throw new AndroidCryptoUtilsException(
					"Parse to JCE RSAPublicKey error: "+e,e.getCause());
		}
	}

	/**
	 * Save the RSA Private key in PKCS8 format using DER encoding
	 * 
	 * @param publicKey
	 *            Public key to be saved
	 * @param publicKeyFullPath
	 *            Path and name of the file in which the key will be saved
	 * @throws AndroidCryptoUtilsException
	 */
	public void savePublicKeyDER(org.spongycastle.asn1.pkcs.RSAPublicKey publicKey,
			String publicKeyFullPath) throws AndroidCryptoUtilsException {
		byte[] serializedPublicBytes;
		try {
			serializedPublicBytes = publicKey.toASN1Primitive().getEncoded(
					"DER");
			byte[] key = Base64.encode(serializedPublicBytes);

			File file = new File(publicKeyFullPath);
			FileOutputStream fos = new FileOutputStream(file);
			fos.write(key);
			fos.flush();
			fos.close();
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			throw new AndroidCryptoUtilsException("[savePublicKeyDER]: " + e,
					e.getCause());
		} catch (IOException e) {
			// TODO Auto-generated catch block
			throw new AndroidCryptoUtilsException("[savePublicKeyDER]: " + e,
					e.getCause());
		}
	}
	
	/**
	 * Generic parser, stores the result in this object
	 * 
	 * @param publicKey
	 *            Private key object, supported object types are:
	 *            {@link org.spongycastle.crypto.params.RSAKeyParameters}
	 *            , {@link org.spongycastle.asn1.pkcs.RSAPublicKey},
	 *            {@link java.security.interfaces.RSAPublicKey}
	 * @throws AndroidCryptoUtilsException
	 */
	public void parseRSAPublicKey(Object publicKey)
			throws AndroidCryptoUtilsException {
		if (publicKey instanceof RSAKeyParameters) {
			parseRSAPublicKey((RSAKeyParameters) publicKey);
		} else if (publicKey instanceof org.spongycastle.asn1.pkcs.RSAPublicKey) {
			parseRSAPublicKey((org.spongycastle.asn1.pkcs.RSAPublicKey) publicKey);
		} else if (publicKey instanceof java.security.interfaces.RSAPublicKey) {
			parseRSAPublicKey((java.security.interfaces.RSAPublicKey) publicKey);
		} else {
			throw new AndroidCryptoUtilsException(
					"Parse error: Public key object type not supported");
		}
	}

	/**
	 * Load the Public key from PKCS8 DER encoded file
	 * 
	 * @param publicKeyFullPath
	 *            Path of the public key
	 * @return Public key read from the file
	 * @throws AndroidCryptoUtilsException
	 */
	public RSAPublicKey loadPublicKeyDER(String publicKeyFullPath)
			throws AndroidCryptoUtilsException {
		try {
			File file = new File(publicKeyFullPath);
			if (file.exists()) {
				FileInputStream fis;

				fis = new FileInputStream(file);

				byte[] key = new byte[(int) file.length()];
				fis.read(key);
				fis.close();

				RSAPublicKey publicKey = new RSAPublicKey();
				publicKey.parseRSAPublicKey(org.spongycastle.asn1.pkcs.RSAPublicKey.getInstance(key));
				return publicKey;
			} else {
				throw new AndroidCryptoUtilsException(
						"RSA Public Key file not found :" + publicKeyFullPath);
			}
		} catch (FileNotFoundException e) {
			throw new AndroidCryptoUtilsException("[loadPublicKeyDER]: " + e,
					e.getCause());
		} catch (IOException e) {
			throw new AndroidCryptoUtilsException("[loadPublicKeyDER]: " + e,
					e.getCause());
		}
	}
}
